#include <ximera/input/dinput8/mouse.hpp>
#include <ximera/input/dinput8/main.hpp>
#include <ximera/input/messages.hpp>
#include <ximera/common/utils.hpp>
#include <ximera/math/util.hpp>

namespace ximera { namespace input { namespace dinput8
{
    void CIM()
    {
        new Mouse(NULL, SharedPtr<threading::MessageQueue>(), SharedObject<IDirectInput8>());
    }

    class XIMERA_PLUGIN_API MouseDownMessage: public threading::IMessage
    {
    public:
        MouseDownMessage(IMouse* sender, MouseCode const& mouseCode): _sender(sender), _mouseCode(mouseCode)
        {
        }

        virtual void process()
        {
            try
            {
                if (_sender)
                    _sender->onMouseDown()(_sender, _mouseCode);
            }
            catch(...)
            {
            }
        }

    private:
        IMouse* _sender;
        MouseCode _mouseCode;
    };

    class XIMERA_PLUGIN_API MouseUpMessage: public threading::IMessage
    {
    public:
        MouseUpMessage(IMouse* sender, MouseCode const& mouseCode): _sender(sender), _mouseCode(mouseCode)
        {
        }

        virtual void process()
        {
            try
            {
                if (_sender)
                    _sender->onMouseUp()(_sender, _mouseCode);
            }
            catch(...)
            {
            }
        }

    private:
        IMouse* _sender;
        MouseCode _mouseCode;
    };

    Mouse::Mouse(HWND hWnd, SharedPtr<threading::MessageQueue> const& msgqueue, SharedObject<IDirectInput8> const& dinput)
        : DeviceTemplate<IMouse>(hWnd, msgqueue, dinput)
    {
        XIMERA_CHECK(dinput, XIMERA_THROW(InvalidParamException, L"The dinput parameter can't be a Null Pointer"));
        XIMERA_CHECK(msgqueue, XIMERA_THROW(InvalidParamException, L"The msgqueue parameter can't be a Null Pointer"));

        IDirectInputDevice8* didevice = NULL;
        HRESULT hr = _dinput->CreateDevice(GUID_SysMouse, &didevice, NULL);
        XIMERA_CHECK(SUCCEEDED(hr), XIMERA_THROW(MouseException, HResultToString(hr)));
        _device.reset(didevice, false);

        hr = _device->SetDataFormat(&c_dfDIMouse2); 
        XIMERA_CHECK(SUCCEEDED(hr), XIMERA_THROW(MouseException, HResultToString(hr)));

        hr = _device->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
        XIMERA_CHECK(SUCCEEDED(hr), XIMERA_THROW(MouseException, HResultToString(hr)));

        init(3, 8);
   }

    Mouse::~Mouse()
    {
        close();
    }

    bool Mouse::isMousePressed(MouseCode const& mouseCode)
    {
        return isPressed(static_cast<UInt16>(mouseCode.value));
    }

    UInt16 Mouse::rumble(Byte rumbleIndex) const
    {
        return 0;
    }

    void Mouse::rumble(Byte rumbleIndex, UInt16 value)
    {
    }

    void Mouse::calcCoords()
    {
        std::vector<Real>& axis1 = _axis[1];
        std::vector<Int16>& axisInt1 = _axisInt[1];
        std::vector<Real>& axisRel1 = _axisRel[1];
        std::vector<Int16>& axisRelInt1 = _axisRelInt[1];

        for (UInt16 I = 0; I < _axisCount; ++I)
        {
            axis1[I] = static_cast<Real>(axisInt1[I]) / 32767.0f;
            axisRel1[I] = static_cast<Real>(axisRelInt1[I]) / 32767.0f;
        }
    }

    void Mouse::update()
    {
        std::vector<Byte>& btn0 = _button[0];
        std::vector<Byte>& btn1 = _button[1];
        //std::copy(btn1.begin(), btn1.end(), btn0.begin());

        std::vector<Int16>& axisRelInt0 = _axisRelInt[0];
        std::vector<Int16>& axisRelInt1 = _axisRelInt[1];
        //std::copy(axisRelInt1.begin(), axisRelInt1.end(), axisRelInt0.begin());

        std::vector<Real>& axisRel0 = _axisRel[0];
        std::vector<Real>& axisRel1 = _axisRel[1];
        //std::copy(axisRel1.begin(), axisRel1.end(), axisRel0.begin());

        std::vector<Int16>& axisInt0 = _axisInt[0];
        std::vector<Int16>& axisInt1 = _axisInt[1];
        //std::copy(axisInt1.begin(), axisInt1.end(), axisInt0.begin());

        std::vector<Real>& axis0 = _axis[0];
        std::vector<Real>& axis1 = _axis[1];
        //std::copy(axis1.begin(), axis1.end(), axis0.begin());

        HRESULT hr = _device->GetDeviceState(sizeof(DIMOUSESTATE2),(LPVOID)&_mouseState);
        if (FAILED(hr))
        { 
            if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
            {
                hr = _device->Acquire();

                if (FAILED(hr))
                    return;

                hr = _device->GetDeviceState(sizeof(DIMOUSESTATE2),(LPVOID)&_mouseState);
            }
        } 

        for (UInt16 I = 0; I < _buttonCount; ++I)
        {
            btn1[I] = _mouseState.rgbButtons[I];
        }

        if (!_onMouseDown.empty() || !_onMouseUp.empty())
        {
            for (UInt16 I = 0; I < _buttonCount; ++I)
            {
                Byte& btn0v = btn0[I];
                Byte& btn1v = btn1[I];

                if (btn0v != btn1v)
                {
                    if (btn1v & 0x80)
                        _msgqueue->addMessage(new MouseDownMessage(this, MouseCode(static_cast<MouseCode::type>(I))));
                    else
                        _msgqueue->addMessage(new MouseUpMessage(this, MouseCode(static_cast<MouseCode::type>(I))));                        

                    btn0v = btn1v;
                }
            }
        }

        axisRelInt1[0] = (Int16)_mouseState.lX;
        axisRelInt1[1] = (Int16)_mouseState.lY;
        axisRelInt1[2] = (Int16)_mouseState.lZ;

        axisInt1[0] += (Int16)_mouseState.lX;
        axisInt1[1] += (Int16)_mouseState.lY;
        axisInt1[2] += (Int16)_mouseState.lZ;

        calcCoords();

        if (!_onAxisChange.empty())
        {
            for (UInt16 I = 0; I < _axisCount; ++I)
            {
                Real& axis0v = axis0[I];
                Real& axis1v = axis1[I];

                if (ximera::math::util::abs(axis1v - axis0v) >= _threshold)
                {
                    _msgqueue->addMessage(new AxisChangeMessage(this, (Byte)I, axis0v, axis1v, axis1v - axis0v));

                    axis0v = axis1v;
                }
            }
        }

        if (!_onAxisIntChange.empty())
        {
            for (UInt16 I = 0; I < _axisCount; ++I)
            {
                Int16& axis0v = axisInt0[I];
                Int16& axis1v = axisInt1[I];

                if (std::abs(axis1v - axis0v) >= _thresholdInt)
                {
                    _msgqueue->addMessage(new AxisIntChangeMessage(this, (Byte)I, axis0v, axis1v, axis1v - axis0v));

                    axis0v = axis1v;
                }
            }
        }
    }
}}}
